package com.uzhie.component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.uzhie.exception.WarningException;
import com.uzhie.jt808.JT808Session;
import com.uzhie.jt808.JT808SessionManager;
import com.uzhie.jt808.msg.entity.JT_0200;
import com.uzhie.jt808.msg.entity.JT_200.*;
import com.uzhie.util.ConstantKit;
import com.uzhie.util.RedisCacheManager;
import com.uzhie.web.bean.ResponseBean;
import com.uzhie.web.bean.ResponseBodyBean;
import com.uzhie.web.controller.websocket.SocketPost;
import com.uzhie.web.dao.*;
import com.uzhie.web.module.*;
import org.apache.log4j.Logger;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 报警工具组件，负责报警信息的汇总及生成
 */
@Component
public class WarningComponent {

    private Logger logger = Logger.getLogger(this.getClass());
    @Autowired(required = false)
    private RedisCacheManager<Warning> redisCacheManager;
    @Autowired
    private WarningDAO waringDAO;
    @Autowired
    private WarningCollectDAO warningCollectDAO;
    @Autowired(required = false)
    @Qualifier(value = "sessionManager")
    private JT808SessionManager sessionManager;
    @Autowired
    private VehicleDAO vehicleDAO;
    @Autowired
    private SocketPost post;

    @Autowired(required = false)
    private JSDrivingAssistanceSystemWarnDAO jsDrivingAssistanceSystemWarnDAO;
    @Autowired(required = false)
    private JSDriverStatusMonitorSystemWarnDAO jsDriverStatusMonitorSystemWarnDAO;
    @Autowired(required = false)
    private JSTirePressureMonitorSystemWarnDAO jsTirePressureMonitorSystemWarnDAO;
    @Autowired(required = false)
    private JSBlindAreaMonitorSystemWarnDAO jsBlindAreaMonitorSystemWarnDAO;

    /**
     * 新增报警信息
     *
     * @param order     定位指令
     * @param simNumber 车辆内置sim卡
     */
    public List<Warning> addWarning(JT_0200 order, @NotEmpty(message = WarningException.Status.SIM_ERROR) String simNumber) {
        List<Vehicle> vehicles = vehicleDAO.selectVehicleBySimNumber(simNumber);
        JT808Session session = sessionManager.getSession(simNumber);
        String driver = null;
        if (session != null) driver = session.getDriverName();
        final List<Warning> result = new ArrayList<>();
        if (vehicles.size() > 0) {
            Map<String, Warning> warnings = Warning.ReadFromOrderForMap(order, vehicles.get(0), driver);
            for (String key : warnings.keySet()) {
                Warning warning = warnings.get(key);
                String rKey = warning.getSimNumber() + warning.getTypeKey();
                //如果redis内不存在这种类型告警,则添加告警类型
                redisCacheManager.lSet(rKey, warning, 24 * 60 * 60);
                logger.info("报警类型被写入redis， key：" + rKey + " value :" + JSONObject.toJSONString(warning));
                waringDAO.insert(warning);
                result.add(warning);
            }
            if (result.size() > 0)
                new Thread(() -> notifyAllWebSocket(result)).start();
        }
        return result;
    }

    public void addJSWarning(JS_0200 js0200, String simNumber) {
        List<Vehicle> vehicles = vehicleDAO.selectVehicleBySimNumber(simNumber);
        JT808Session session = sessionManager.getSession(simNumber);
        String driver = "";
        if (session != null) {
            driver = session.getDriverName();
            driver = driver==null?"":driver;
        }
        if (vehicles.size() > 0) {
            // TODO: 2018/10/7 写入苏标报警信息到数据库
            Date now = new Date(System.currentTimeMillis());
            addDrivingAssistanceSystemWarn(js0200, now, vehicles.get(0), driver);
            addDriverStatusMonitorSystemWarn(js0200, now, vehicles.get(0), driver);
            addTirePressureMonitorSystemWarn(js0200, now, vehicles.get(0), driver);
            addBlindAreaMonitorSystemWarn(js0200, now, vehicles.get(0), driver);
        }
    }

    private void addDrivingAssistanceSystemWarn(JS_0200 js0200, Date now, Vehicle vehicle, String driverName) {
        JS_DrivingAssistanceSystemWarn drivingAssistanceSystemWarn = js0200.getDrivingAssistanceSystemWarn();
        if (null == drivingAssistanceSystemWarn) {
            logger.info("无苏标 高级驾驶辅助系统 报警");
            return;
        }
        logger.info("正在处理苏标 高级驾驶辅助系统 报警");
        JSDrivingAssistanceSystemWarn jsDrivingAssistanceSystemWarn = new JSDrivingAssistanceSystemWarn();
        jsDrivingAssistanceSystemWarn.setFlagStatus(drivingAssistanceSystemWarn.getFlagStatus());
        jsDrivingAssistanceSystemWarn.setFrontDest(drivingAssistanceSystemWarn.getFrontDest());
        jsDrivingAssistanceSystemWarn.setFrontSpeed(drivingAssistanceSystemWarn.getFrontSpeed());
        jsDrivingAssistanceSystemWarn.setLevel(drivingAssistanceSystemWarn.getLevel());
        jsDrivingAssistanceSystemWarn.setSpeed(drivingAssistanceSystemWarn.getSpeed());
        jsDrivingAssistanceSystemWarn.setType(drivingAssistanceSystemWarn.getType());
        jsDrivingAssistanceSystemWarn.setLat(drivingAssistanceSystemWarn.getLatitude());
        jsDrivingAssistanceSystemWarn.setLng(drivingAssistanceSystemWarn.getLongitude());
        jsDrivingAssistanceSystemWarn.setCreateTime(now);
        jsDrivingAssistanceSystemWarn.setUpdateTime(now);
        jsDrivingAssistanceSystemWarn.setCatalogId(vehicle.getDepartId());
        jsDrivingAssistanceSystemWarn.setDriverName(driverName);
        jsDrivingAssistanceSystemWarn.setVehicleNo(vehicle.getNumber());
        jsDrivingAssistanceSystemWarnDAO.insertSelective(jsDrivingAssistanceSystemWarn);
    }

    private void addDriverStatusMonitorSystemWarn(JS_0200 js0200, Date now, Vehicle vehicle, String driverName) {
        JS_DriverStatusMonitorSystemWarn driverStatusMonitorSystemWarn = js0200.getDriverStatusMonitorSystemWarn();
        if (null == driverStatusMonitorSystemWarn) {
            logger.info("无苏标 驾驶员状态监测系统 报警");
            return;
        }
        logger.info("正在处理苏标 驾驶员状态监测系统 报警");
        JSDriverStatusMonitorSystemWarn jsDriverStatusMonitorSystemWarn = new JSDriverStatusMonitorSystemWarn();
        jsDriverStatusMonitorSystemWarn.setFlagStatus(driverStatusMonitorSystemWarn.getFlagStatus());
        jsDriverStatusMonitorSystemWarn.setVehicleNo(vehicle.getNumber());
        jsDriverStatusMonitorSystemWarn.setDriverName(driverName);
        jsDriverStatusMonitorSystemWarn.setLat(driverStatusMonitorSystemWarn.getLatitude());
        jsDriverStatusMonitorSystemWarn.setLng(driverStatusMonitorSystemWarn.getLongitude());
        jsDriverStatusMonitorSystemWarn.setCatalogId(vehicle.getDepartId());
        jsDriverStatusMonitorSystemWarn.setLevel(driverStatusMonitorSystemWarn.getLevel());
        jsDriverStatusMonitorSystemWarn.setSpeed(driverStatusMonitorSystemWarn.getSpeed());
        jsDriverStatusMonitorSystemWarn.setTiredLevel(driverStatusMonitorSystemWarn.getTiredLevel());
        jsDriverStatusMonitorSystemWarn.setType(driverStatusMonitorSystemWarn.getType());
        jsDriverStatusMonitorSystemWarn.setReportTime(driverStatusMonitorSystemWarn.getTime());
        jsDriverStatusMonitorSystemWarn.setCreateTime(now);
        jsDriverStatusMonitorSystemWarn.setUpdateTime(now);
        jsDriverStatusMonitorSystemWarnDAO.insertSelective(jsDriverStatusMonitorSystemWarn);
    }

    private void addTirePressureMonitorSystemWarn(JS_0200 js0200, Date now, Vehicle vehicle, String driverName) {
        JS_TirePressureMonitorSystemWarn tirePressureMonitorSystemWarn = js0200.getTirePressureMonitorSystemWarn();
        if (null == tirePressureMonitorSystemWarn) {
            logger.info("无苏标 胎压监测 系统报警");
            return;
        }
        logger.info("正在处理苏标 胎压监测 系统报警");
        JSTirePressureMonitorSystemWarn jsTirePressureMonitorSystemWarn = new JSTirePressureMonitorSystemWarn();
        jsTirePressureMonitorSystemWarn.setReportTime(tirePressureMonitorSystemWarn.getTime());
        jsTirePressureMonitorSystemWarn.setUpdateTime(now);
        jsTirePressureMonitorSystemWarn.setCreateTime(now);
        jsTirePressureMonitorSystemWarn.setType(tirePressureMonitorSystemWarn.getType());
        jsTirePressureMonitorSystemWarn.setBattery(tirePressureMonitorSystemWarn.getBattery());
        jsTirePressureMonitorSystemWarn.setTireTemp(tirePressureMonitorSystemWarn.getTireTemp());
        jsTirePressureMonitorSystemWarn.setTirePressure(tirePressureMonitorSystemWarn.getTirePressure());
        jsTirePressureMonitorSystemWarn.setTyrePos(tirePressureMonitorSystemWarn.getTyrePos());
        jsTirePressureMonitorSystemWarn.setCatalogId(vehicle.getDepartId());
        jsTirePressureMonitorSystemWarn.setDriverName(driverName);
        jsTirePressureMonitorSystemWarn.setVehicleNo(vehicle.getNumber());
        jsTirePressureMonitorSystemWarn.setLat(tirePressureMonitorSystemWarn.getLatitude());
        jsTirePressureMonitorSystemWarn.setLng(tirePressureMonitorSystemWarn.getLongitude());
        jsTirePressureMonitorSystemWarnDAO.insertSelective(jsTirePressureMonitorSystemWarn);
    }

    private void addBlindAreaMonitorSystemWarn(JS_0200 js0200, Date now, Vehicle vehicle, String driverName) {
        JS_BlindAreaMonitorSystemWarn blindAreaMonitorSystemWarn = js0200.getBlindAreaMonitorSystemWarn();
        if (null == blindAreaMonitorSystemWarn) {
            logger.info("无苏标 盲区监测 系统报警");
            return;
        }
        logger.info("正在处理苏标 盲区监测 系统报警");
        JSBlindAreaMonitorSystemWarn jsBlindAreaMonitorSystemWarn = new JSBlindAreaMonitorSystemWarn();
        jsBlindAreaMonitorSystemWarn.setLat(blindAreaMonitorSystemWarn.getLatitude());
        jsBlindAreaMonitorSystemWarn.setLng(blindAreaMonitorSystemWarn.getLongitude());
        jsBlindAreaMonitorSystemWarn.setCatalogId(vehicle.getDepartId());
        jsBlindAreaMonitorSystemWarn.setDriverName(driverName);
        jsBlindAreaMonitorSystemWarn.setSpeed(blindAreaMonitorSystemWarn.getSpeed());
        jsBlindAreaMonitorSystemWarn.setType(blindAreaMonitorSystemWarn.getType());
        jsBlindAreaMonitorSystemWarn.setCreateTime(now);
        jsBlindAreaMonitorSystemWarn.setUpdateTime(now);
        jsBlindAreaMonitorSystemWarn.setReportTime(blindAreaMonitorSystemWarn.getTime());
        jsBlindAreaMonitorSystemWarn.setVehicleNo(vehicle.getNumber());
        jsBlindAreaMonitorSystemWarnDAO.insertSelective(jsBlindAreaMonitorSystemWarn);
    }

    /**
     * 通知前端进行报警音视频更新
     *
     * @param warnings
     */
    public void notifyAllWebSocket(List<Warning> warnings) {
        //TODO 告警预留
        ResponseBodyBean bodyBean = new ResponseBodyBean();
        bodyBean.setResult(warnings);
        ResponseBean bean = new ResponseBean();
        bean.setResponseBody(bodyBean);
        post.postAll((JSONObject) JSONObject.toJSON(bean));
    }

    /**
     * 告警信息的汇总保存
     *
     * @param simNumber
     */
    public void addWarningCollection(String simNumber) {
        List<WarningCollect> collects = new ArrayList<>();
        //对redis中的所有报警信息进行判定，确认连续报警的存储逻辑
        logger.info("接收到告警汇总消息");
        for (ConstantKit.MEDIAWARN eum : ConstantKit.MEDIAWARN.values()) {
            //轮询报警类型
            int type = eum.getType();
            //由车载sim卡与报警类型组成key，保证redis中的每辆车的报警信息可以纵向隔离
            String key = simNumber + type;
            //锁定对应key的当前时间长度，避免多线程读写时产生io异常
            long size = redisCacheManager.lGetListSize(key);
            if (size == 0) continue;
            //通过key从redis中获取报警信息
            Warning last = redisCacheManager.lGetIndex(simNumber + type, -1);
            //如果报警信息不等于空，判断是否存在连续报警
            if (last != null) {
                Warning lastButOne = redisCacheManager.lGetIndex(simNumber + type, -2);
                //如果倒数第二个报警位不等于空，则证明当前存在连续报警
                if (lastButOne != null) {
                    logger.info("存在连续报警，判断时间间隔是否进行存储");
                    //如果连续报警时间超过60秒，则判定属于两次阶段性报警，对上一阶段报警进行存储，并从redis中移除
                    if (last.getReportTime().getTime() - lastButOne.getReportTime().getTime() > 60000 || size > 2 * 60 * 24 || (System.currentTimeMillis() - lastButOne.getReportTime().getTime() > 300000)) {
                        logger.info("存在连续报警，可以进行储存，报警类型为:" + last.getType() + ", 报警时间为 结束时间:" + last.getReportTime() + "redisKey ：" + key);
                        //根据前面锁定的长度，获取上一阶段的告警信息
                        List<Warning> warns = redisCacheManager.lGet(key, 0, size - 1);
                        //从redis中移除已经汇总的报警信息
                        redisCacheManager.ltrim(key, size, -1);
                        logger.info("存在不连续报警，可以进行储存，报警类型为:" + last.getType() + ", 报警时间为 结束时间:" + last.getReportTime() + "redisKey ：" + key);
                        WarningCollect collect = WarningCollect.ReadFromOrder(warns);
                        collects.add(collect);
                        break;
                    } else
                        logger.info("存在连续报警，但是时间间隔小于30秒，不需要存储，redisKey ：" + key);
                } else
                    logger.info("redis中不存在类型为；-----" + type + "-----的连续报警redisKey ：" + key);
            } else
                logger.info("redis中不存在类型为；" + type + "的报警redisKey ：" + key);

        }
        //当报警集合信息不等于空的时候
        if (collects.size() > 0) {
            warningCollectDAO.insertGroup(collects);
            logger.info("存在告警汇总消息，消息长度为 " + collects.size() + "内容为" + JSON.toJSONString(collects));
        }
    }
}
